/*******************************************************************************
 * @file Copyright 2009 The CREATE Lab at CMU
 * 
 *       This file is part of JubJub, an open source project.
 *       http://code.google.com/p/jubjub/
 * 
 *       Redistribution and use in source and binary forms, with or without
 *       modification, are permitted provided that the following conditions are
 *       met:
 * 
 *       Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 * 
 *       Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 * 
 *       Neither the name of Carnegie Mellon nor the names of its contributors
 *       may be used to endorse or promote products derived from this software
 *       without specific prior written permission.
 * 
 *       THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *       "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *       LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 *       PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 *       HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 *       SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 *       LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *       DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *       THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *       (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 *       OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *******************************************************************************/

package edu.cmu.ri.createlab.jj.visitors;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import org.w3c.dom.*;
import org.xml.sax.*;

import javax.xml.parsers.*;

import edu.cmu.ri.createlab.jj.blocks.*;
import edu.cmu.ri.createlab.jj.blocks.BlockDefs.*;
import edu.cmu.ri.createlab.jj.projects.*;

public class JJXMLParser
{
   enum VarPlacement
   {
      VARS,
      PARAMS,
      RETURNS
   }
   private DocumentBuilder builder;
   JJProject jjproj;
   JJLibrary currLib;
   VarPlacement currPlace;
   BlockInterface currBlock;
   
   Hashtable<Integer, BlockInterface> blocks;
   ArrayList<RefBlock> refs;
   
   public JJXMLParser(DocumentBuilderFactory dbf) 
      throws ParserConfigurationException
   {
      builder = dbf.newDocumentBuilder();
      blocks = new Hashtable<Integer, BlockInterface>();
      refs = new ArrayList<RefBlock>();
      jjproj = null;
      currLib = null;
      currBlock = null;
      currPlace = null;
   }
   
   public void clear()
   {
      jjproj = null;
      currLib = null;
      currBlock = null;
      currPlace = null;
   }
   
   
   private void addBlock(BlockInterface block, int ID)
   {
      blocks.put(new Integer(ID),block);
   }
   
   private BlockInterface getBlock(int ID)
   {
      return blocks.get(new Integer(ID));
   }
   
   private List<VarBlock> readIO(Node n) 
      throws Exception
   {
      LinkedList<VarBlock> varList = new LinkedList<VarBlock>();
      NodeList nodes = n.getChildNodes();
      for(int i = 0; i < nodes.getLength(); i++)
      {
         Node node = nodes.item(i);
         if(node.getNodeType() == Node.ELEMENT_NODE)
         {
            String tag = ((Element)node).getTagName();
            if(tag.equals("VarBlock"))
               varList.add(parseVar(node));
         }
      }
      return varList;
   }
   
   private List<RefBlock> readRefs(Node n)
      throws JJException
   {
      LinkedList<RefBlock> refs = new LinkedList<RefBlock>();
      NodeList nodes = n.getChildNodes();
      for(int i = 0; i < nodes.getLength(); i++)
      {
         Node node = nodes.item(i);
         if(node.getNodeType() == Node.ELEMENT_NODE)
         {
            String tag = ((Element)node).getTagName();
            if(tag.equals("ref"))
               refs.add(parseRef(node));
         }
      }
      return refs;
   }
   
   private List<TaggedCodePiece> readTaggedCode(Node n)
   {
      LinkedList<TaggedCodePiece> tcpList = new LinkedList<TaggedCodePiece>();
      ArrayList<String> varNames = new ArrayList<String>();
      NodeList nodes = n.getChildNodes();
      for(int i = 0; i < nodes.getLength(); i++)
      {
         Node node = nodes.item(i);
         if(node.getNodeType() == Node.ELEMENT_NODE)
         {
            String tag = ((Element)node).getTagName();
            if(tag.equals("code"))
               tcpList.add(new TaggedCodePiece(node.getChildNodes().item(0).getNodeValue()));
            else if(tag.equals("var"))
            {
               String varName = node.getChildNodes().item(0).getNodeValue();
               int index = varNames.indexOf(varName);
               if(index == -1)
               {
                  varNames.add(varName);
                  index = varNames.indexOf(varName);
               }
               tcpList.add(new TaggedCodePiece(varName, index));
            }
         }
      }
      return tcpList;
   }
   
   ///Refs can be in lots of places so we just return it and let the caller
   ///place it. Should probably adopt the same method for other blocks too,
   ///but we're too far in to change that now. Maybe in the next revision.
   private RefBlock parseRef(Node n)
      throws JJException
   {
      RefBlock block;
      String name = null;
      String ID = null;
      String comment = null;
      List<RefBlock> params = null;
      List<RefBlock> returns = null;
      
      NodeList nodes = n.getChildNodes();
      for(int i = 0; i < nodes.getLength(); i++)
      {
         Node node = nodes.item(i);
         if(node.getNodeType() == Node.ELEMENT_NODE)
         {
            String tag = ((Element)node).getTagName();
            if(tag.equals("name"))
               name = node.getChildNodes().item(0).getNodeValue();
            else if(tag.equals("id"))
               ID = node.getChildNodes().item(0).getNodeValue();
            else if(tag.equals("comment"))
               comment = node.getChildNodes().item(0).getNodeValue();
            else if(tag.equals("params"))
               params = readRefs(node);
            else if(tag.equals("returns"))
               returns = readRefs(node);
         }
      }
      if(ID == null || ID.length() == 0)
         throw new JJException("Error:Unfinished or malformed RefBlock.");
      block = new RefBlock(Integer.parseInt(ID.trim()), null);
      if(name != null) block.setName(name);
      if(comment != null) block.setComment(comment);
      if(params != null)
      {
         Iterator<RefBlock> it = params.iterator();
         while(it.hasNext())
            block.addParam(it.next());
      }
      if(returns != null)
      {
         Iterator<RefBlock> it = returns.iterator();
         while(it.hasNext())
            block.addReturn(it.next());
      }
      
      refs.add(block);
      return block;
   }
   
   private CodeBlock parseCode(Node n) 
      throws Exception
   {
      CodeBlock block;
      String name = null;
      String ID = null;
      String comment = null;
      String prefix = null;
      String cType = null;
      String construct = null;
      String[] access = {null, null, null, null, null};
      int accIndex = 0;
      List<VarBlock> params = null;
      List<VarBlock> returns = null;
      List<TaggedCodePiece> taggedCode = null;
      
      NodeList nodes = n.getChildNodes();
      for(int i = 0; i < nodes.getLength(); i++)
      {
         Node node = nodes.item(i);
         if(node.getNodeType() == Node.ELEMENT_NODE)
         {
            String tag = ((Element)node).getTagName();
            if(tag.equals("name"))
               name = node.getChildNodes().item(0).getNodeValue();
            else if(tag.equals("id"))
               ID = node.getChildNodes().item(0).getNodeValue();
            else if(tag.equals("prefix"))
               prefix = node.getChildNodes().item(0).getNodeValue();
            else if(tag.equals("callType"))
               cType = node.getChildNodes().item(0).getNodeValue();
            else if(tag.equals("comment"))
               comment = node.getChildNodes().item(0).getNodeValue();
            else if(tag.equals("constructor"))
               construct = node.getChildNodes().item(0).getNodeValue();
            else if(tag.equals("access"))
            {
               if(accIndex < access.length)
                  access[accIndex++] = node.getChildNodes().item(0).getNodeValue();
            }
            else if(tag.equals("params"))
               params = readIO(node);
            else if(tag.equals("returns"))
               returns = readIO(node);
            else if(tag.equals("taggedCode"))
               taggedCode = readTaggedCode(node);
         }
      }

      if(name == null || name.length() == 0 || ID == null || ID.length() == 0)
         throw new JJException("Error:Unfinished or malformed CodeBlock.");
      block = new CodeBlock(name, null, Integer.parseInt(ID.trim()));
      if(prefix != null && !(prefix.length() == 0))
         block.setPrefix(prefix);
      if(comment != null && !(comment.length() == 0))
         block.setComment(comment);
      if(cType != null && cType.equals(CallTypes.INLINE.toString()))
         block.setCallType(CallTypes.INLINE);
      if(accIndex != 0)
      {
         int accCount = 0;
         AccessTypes[] acc = new AccessTypes[accIndex];
         for(int i = 0; i < accIndex; i++)
         {
            for(AccessTypes at : AccessTypes.values())
            {
               if(access[i].equals(at.toString()))
               {
                  acc[accCount++] = at;
               }
            }
         }
         if(accCount != accIndex)
            throw new JJException("Error:Misnamed or empty AccessType");
         block.setAccess(acc);
      }
      if(construct != null && construct.equals("true"))
         block.setConstructor(true);
      if(params != null)
      {
         Iterator<VarBlock> vIt = params.iterator();
         while(vIt.hasNext())
            block.addParam(vIt.next());
      }
      if(returns != null)
      {
         Iterator<VarBlock> vIt = returns.iterator();
         while(vIt.hasNext())
            block.addReturn(vIt.next());
      }
      if(taggedCode != null)
         block.setTaggedCode(taggedCode);
      
      addBlock(block, block.getID());
      return block;
   }
   
   private BlockBlock parseBlock(Node n) 
      throws Exception
   {
      BlockBlock block;
      String name = null;
      String ID = null;
      String comment = null;
      String prefix = null;
      String cType = null;
      String construct = null;
      String[] access = {null, null, null, null, null};
      int accIndex = 0;
      List<VarBlock> vars = null;
      List<VarBlock> params = null;
      List<RefBlock> returns = null;
      List<RefBlock> bCode = null;

      NodeList nodes = n.getChildNodes();
      for(int i = 0; i < nodes.getLength(); i++)
      {
         Node node = nodes.item(i);
         if(node.getNodeType() == Node.ELEMENT_NODE)
         {
            String tag = ((Element)node).getTagName();
            if(tag.equals("name"))
               name = node.getChildNodes().item(0).getNodeValue();
            else if(tag.equals("id"))
               ID = node.getChildNodes().item(0).getNodeValue();
            else if(tag.equals("prefix"))
               prefix = node.getChildNodes().item(0).getNodeValue();
            else if(tag.equals("callType"))
               cType = node.getChildNodes().item(0).getNodeValue();
            else if(tag.equals("comment"))
               comment = node.getChildNodes().item(0).getNodeValue();
            else if(tag.equals("constructor"))
               construct = node.getChildNodes().item(0).getNodeValue();
            else if(tag.equals("access"))
            {
               if(accIndex < access.length)
                  access[accIndex++] = node.getChildNodes().item(0).getNodeValue();
            }
            else if(tag.equals("vars"))
               vars = readIO(node);
            else if(tag.equals("params"))
               params = readIO(node);
            else if(tag.equals("returns"))
               returns = readRefs(node);
            else if(tag.equals("blockCode"))
               bCode = readRefs(node);
         }
      }
      if(name == null || name.length() == 0 || ID == null || ID.length() == 0)
         throw new JJException("Error:Unfinished or malformed CodeBlock.");
      block = new BlockBlock(name, null, Integer.parseInt(ID.trim()));
      if(prefix != null && !(prefix.length() == 0))
         block.setPrefix(prefix);
      if(comment != null && !(comment.length() == 0))
         block.setComment(comment);
      if(cType != null && cType.equals(CallTypes.INLINE.toString()))
         block.setCallType(CallTypes.INLINE);
      if(accIndex != 0)
      {
         int accCount = 0;
         AccessTypes[] acc = new AccessTypes[accIndex];
         for(int i = 0; i < accIndex; i++)
         {
            for(AccessTypes at : AccessTypes.values())
            {
               if(access[i].equals(at.toString()))
               {
                  acc[accCount++] = at;
               }
            }
         }
         if(accCount != accIndex)
            throw new JJException("Error:Misnamed or empty AccessType");
         block.setAccess(acc);
      }
      if(construct != null && construct.equals("true"))
         block.setConstructor(true);
      if(vars != null)
      {
         Iterator<VarBlock> vIt = vars.iterator();
         while(vIt.hasNext())
            block.addVar(vIt.next());
      }
      if(params != null)
      {
         Iterator<VarBlock> vIt = params.iterator();
         while(vIt.hasNext())
            block.addParam(vIt.next());
      }
      if(returns != null)
      {
         Iterator<RefBlock> vIt = returns.iterator();
         while(vIt.hasNext())
            block.addReturn(vIt.next());
      }
      if(bCode != null)
      {
         Iterator<RefBlock> rIt = bCode.iterator();
         while(rIt.hasNext())
            block.addCode(rIt.next());
      }
      
      addBlock(block, block.getID());
      return block;
   }
   
   private FlowBlock parseFlow(Node n)
      throws Exception
   {
      FlowBlock block;
      String name = null;
      String ID = null;
      String comment = null;
      String middles = null;
      List<VarBlock>[] vars = new List[4];
      List<TaggedCodePiece>[] codes = new List[4];
      for(int i = 0; i < 4; i++)
      {
         codes[i] = null;
         vars[i] = null;
      }
      NodeList nodes = n.getChildNodes();
      for(int i = 0; i < nodes.getLength(); i++)
      {
         Node node = nodes.item(i);
         if(node.getNodeType() == Node.ELEMENT_NODE)
         {
            String tag = ((Element)node).getTagName();
            if(tag.equals("name"))
               name = node.getChildNodes().item(0).getNodeValue();
            else if(tag.equals("id"))
               ID = node.getChildNodes().item(0).getNodeValue();
            else if(tag.equals("comment"))
               comment = node.getChildNodes().item(0).getNodeValue();
            else if(tag.equals("middles"))
               middles = node.getChildNodes().item(0).getNodeValue();
            else if(tag.equals("params0"))
            {
               vars[0] = readIO(node);
                  
            }
            else if(tag.equals("params1"))
            {
               vars[1] = readIO(node);
               
            }
            else if(tag.equals("params2"))
            {
               vars[2] = readIO(node);
            }
            else if(tag.equals("params3"))
            {
               vars[3] = readIO(node);
            }
            else if(tag.equals("code0"))
            {
               codes[0] = readTaggedCode(node);
            }
            else if(tag.equals("code1"))
            {
               codes[1] = readTaggedCode(node);
            }
            else if(tag.equals("code2"))
            {
               codes[2] = readTaggedCode(node);
            }
            else if(tag.equals("code3"))
            {
               codes[3] = readTaggedCode(node);
            }
            
         }
      }
      if(name == null || name.length() == 0 || ID == null || ID.length() == 0)
         throw new JJException("Error:Unfinished or misformed FlowBlock.");
      block = new FlowBlock(name, null, Integer.parseInt(ID.trim()));
      for(int i = 0; i < vars.length; i++)
      {
         if(vars[i] != null) block.setParams(vars[i], i);
         if(codes[i] != null) block.setCode(codes[i], i);
      }
      if(middles != null && !(middles.length() == 0))
         block.setMiddles(Integer.parseInt(middles.trim()));
      if(comment != null)
         block.setComment(comment);
      addBlock(block, block.getID());
      
      return block;
   }
   
   //TODO Specify which exceptions everything throws so specefic catches can be made
   private ExpressionBlock parseExpression(Node n)
      throws Exception
   {
      ExpressionBlock block;
      String ID = null;
      String comment = null;
      String expType = null;
      String symbol = null;
      RefBlock[] refs = {null, null};
      int refPos = 0;
      
      NodeList nodes = n.getChildNodes();
      for(int i = 0; i < nodes.getLength(); i++)
      {
         Node node = nodes.item(i);
         if(node.getNodeType() == Node.ELEMENT_NODE)
         {
            String tag = ((Element)node).getTagName();
            if(tag.equals("id"))
               ID = node.getChildNodes().item(0).getNodeValue();
            else if(tag.equals("comment"))
               comment = node.getChildNodes().item(0).getNodeValue();
            else if(tag.equals("etype"))
               expType = node.getChildNodes().item(0).getNodeValue();
            else if(tag.equals("symbol"))
               symbol = node.getChildNodes().item(0).getNodeValue();
            else if(tag.equals("ref") && refPos < 2)
               refs[refPos++] = parseRef(node);
         }
      }
      
      if(ID == null || ID.length() == 0 || expType == null || expType.length() == 0
            || symbol == null || symbol.length() == 0)
      {
         throw new JJException("Error:Unfinished or malformed Expression Block");
      }
      int id = Integer.parseInt(ID.trim());
      ExpressTypes eType = null;
      for(ExpressTypes et : ExpressTypes.values())
      {
         if(expType.equals(et.toString()))
            eType = et;
      }
      block = new ExpressionBlock(null, id, eType, symbol);
      if(refs[0] != null) block.setFirstIn(refs[0]);
      if(refs[1] != null) block.setSecondIn(refs[1]);
      if(comment != null) block.setComment(comment);
      
      addBlock(block, block.getID());
      return block;
   }
   
   private VarBlock parseVar(Node n)
      throws Exception
   {
      VarBlock block;
      String name = null;
      String ID = null;
      String comment = null;
      String[] access = new String[5];
      int accIndex = 0;
      String varType = null;
      String literal = null;
      String length = null;
      String[] values = null;
      int valIndex = 0;
      String objectType = null;
      
      NodeList nodes = n.getChildNodes();
      for(int i = 0; i < nodes.getLength(); i++)
      {
         Node node = nodes.item(i);
         if(node.getNodeType() == Node.ELEMENT_NODE)
         {
            String tag = ((Element)node).getTagName();
            if(tag.equals("name"))
               name = node.getChildNodes().item(0).getNodeValue();
            else if(tag.equals("id"))
               ID = node.getChildNodes().item(0).getNodeValue().trim();
            else if(tag.equals("comment"))
               comment = node.getChildNodes().item(0).getNodeValue();
            else if(tag.equals("access"))
               access[accIndex++] = node.getChildNodes().item(0).getNodeValue();
            else if(tag.equals("varType"))
               varType = node.getChildNodes().item(0).getNodeValue();
            else if(tag.equals("literal"))
               literal = node.getChildNodes().item(0).getNodeValue();
            else if(tag.equals("length"))
            {
               length = node.getChildNodes().item(0).getNodeValue().trim();
               values = new String[Integer.parseInt(length)];
            }
            else if(tag.equals("value"))
            {
               if(values == null || valIndex > values.length)
                  throw new JJException("Error:Values Length mismatch.");
               values[valIndex++] = node.getChildNodes().item(0).getNodeValue().trim();
            }
            else if(tag.equals(BlockTypes.OBJECT.toString()))
            {
               if(ID == null || ID.length() == 0)
                  throw new JJException("Error:No id or misplaced id in Var.");
               ///If this is an object then just let the object create it
               ObjectBlock ob = parseObject(node, Integer.parseInt(ID));
               ///and return;
               return ob.getVariable();
            }
            else if(tag.equals("objectType"))
               objectType = node.getChildNodes().item(0).getNodeValue();
         }
      }
      if(name == null || name.length() == 0 || ID == null || ID.length() == 0
            || varType == null || varType.length() == 0)
      {
         throw new JJException("Error:Unfinished or malformed VarBlock.");
      }
      else
      {
         boolean lit;
         VarTypes vType = null;;
         int id = Integer.parseInt(ID.trim());
         if(literal != null && literal.equals("true"))
            lit = true;
         else lit = false;
         varType = varType.trim();
         for(VarTypes vt : VarTypes.values())
         {
            if(varType.equals(vt.toString()))
               vType = vt;
         }
         if(vType == null)
            throw new JJException("Error:No valid vType found.");
         block = new VarBlock(name, null, id, vType, lit);
         if(comment != null) block.setComment(comment);
         if(length != null && !(length.length() == 0))
            block.setLength(Integer.parseInt(length.trim()));
         if(objectType != null && !(objectType.length() == 0))
            block.setObjectType(objectType);
         if(values != null)
         {
            for(int i = 0; i < values.length; i++)
            {
               block.setValue(values[i], i);
            }
         }
         if(accIndex != 0)
         {
            int accCount = 0;
            AccessTypes[] acc = new AccessTypes[accIndex];
            for(int i = 0; i < accIndex; i++)
            {
               for(AccessTypes at : AccessTypes.values())
               {
                  if(access[i].equals(at.toString()))
                  {
                     acc[accCount++] = at;
                  }
               }
            }
            if(accCount != accIndex)
               throw new JJException("Error:Misnamed or empty AccessType");
            block.setAccess(acc);
         }
         ///Add it to the block list
         addBlock(block, id);
         return block;
      }
         
   }
   
   private ObjectBlock parseObject(Node n, int varID)
      throws Exception
   {
      ObjectBlock block;
      String name = null;
      String ID = null;
      String comment = null;
      String cName = null;
      String objType = null;
      String length = null;
      
      NodeList nodes = n.getChildNodes();
      for(int i = 0; i < nodes.getLength(); i++)
      {
         Node node = nodes.item(i);
         if(node.getNodeType() == Node.ELEMENT_NODE)
         {
            String tag = ((Element)node).getTagName();
            if(tag.equals("name"))
               name = node.getChildNodes().item(0).getNodeValue();
            else if(tag.equals("id"))
               ID = node.getChildNodes().item(0).getNodeValue().trim();
            else if(tag.equals("comment"))
               comment = node.getChildNodes().item(0).getNodeValue();
            else if(tag.equals("cName"))
               cName = node.getChildNodes().item(0).getNodeValue();
            else if(tag.equals("objectType"))
               objType = node.getChildNodes().item(0).getNodeValue();
            else if(tag.equals("length"))
               length = node.getChildNodes().item(0).getNodeValue().trim();
         }
      }
      if(name == null || name.length() == 0 || ID == null || ID.length() == 0)
      {
         throw new JJException("Error:Unfinished Object Block.");
      }
      else
      {
         int id = Integer.parseInt(ID);
         JJClass jjclass;
         if(cName == null || cName.length() == 0)
            jjclass = null;
         else if(currLib != null)
            jjclass = currLib.getClass(cName);
         else jjclass = jjproj.getClass(cName);
         block = new ObjectBlock(name, null, id, varID, jjclass);
         if(comment != null) block.getVariable().setComment(comment);
         if(length != null && !(length.length() == 0))
            block.setLength(Integer.parseInt(length));
         if(objType != null)
            block.setObjectType(objType);
         
         addBlock(block, id);
         addBlock(block.getVariable(), varID);
         return block;
      }
      
   }
   
   private CommentBlock parseComment(Node n)
      throws Exception
   {
      NodeList nodes = n.getChildNodes();
      CommentBlock block = null;
      String name = null;
      String ID = null;
      String comment = null;
      for(int i = 0; i < nodes.getLength(); i++)
      {
         Node node = nodes.item(i);
         if(node.getNodeType() == Node.ELEMENT_NODE)
         {
            String tag = ((Element)node).getTagName();
            if(tag.equals("name"))
            {
               name = node.getChildNodes().item(0).getNodeValue();
            }
            else if(tag.equals("id"))
            {
               ID = node.getChildNodes().item(0).getNodeValue().trim();
            }
            else if(tag.equals("comment"))
            {
               comment = node.getChildNodes().item(0).getNodeValue();
            }
         }
      }
      if(name == null || ID == null || name.length() == 0 || ID.length() == 0)
      {
         throw new JJException("Error:Unfinished comment block.");
      }
      int id = Integer.parseInt(ID);
      block = new CommentBlock(name, null, id);
      block.setComment(comment);
      addBlock(block, id);
      return block;
   }
   
   
   private void parseClass(Node n)
      throws Exception
   {
      NodeList nodes = n.getChildNodes();
      JJClass jjclass = null;
      for(int i = 0; i < nodes.getLength(); i++)
      {
         Node node = nodes.item(i);
         if(node.getNodeType() == Node.ELEMENT_NODE)
         {
            String tag = ((Element)node).getTagName();
            if(tag.equals("name"))
            {
               if(jjclass != null)
                  throw new JJException("Error:Multiple class level 'name' tags.");
               String cName = node.getChildNodes().item(0).getNodeValue();
               if(currLib != null) jjclass = currLib.getClass(cName);
               else jjclass = jjproj.getClass(cName);
            }
            else if(jjclass == null)
            {
               throw new JJException("Error:First element in class must be 'name'.");
            }
            else if(tag.equals(BlockTypes.VAR.toString()))
            {
               VarBlock var = parseVar(node);
               jjclass.addVar(var);
               var.setOwner(jjclass);
               
            }
            else if(tag.equals(BlockTypes.CODE.toString()))
            {
               //TODO add code to correctly place returned blocks
               CodeBlock block = parseCode(node);
               if(block.isConstructor())
                  jjclass.addConstructor(block);
               else if(block.getCallType() == CallTypes.INLINE)
                  jjclass.addInlineBlock(block);
               else jjclass.addMethodBlock(block);
               block.setOwner(jjclass);
            }
            else if(tag.equals(BlockTypes.BLOCK.toString()))
            {
               BlockBlock block = parseBlock(node);
               if(block == null);
               else if(block.isConstructor())
                  jjclass.addConstructor(block);
               else if(block.getCallType() == CallTypes.INLINE)
                  jjclass.addInlineBlock(block);
               else jjclass.addMethodBlock(block);
               block.setOwner(jjclass);
            }
            else if(tag.equals("comment"))
            {
               jjclass.setComment(node.getChildNodes().item(0).getNodeValue());
            }
         }
      }
   }
   
   ///parse a tab
   private JJTab parseTab(Node n)
      throws Exception
   {
      return null;
   }
   
   ///parse all the free blocks
   private void parseFree(Node n) 
      throws Exception
   {
      NodeList nodes = n.getChildNodes();
      for(int i = 0; i < nodes.getLength(); i++)
      {
         Node node = nodes.item(i);
         if(node.getNodeType() == Node.ELEMENT_NODE)
         {
            String tag = ((Element)node).getTagName();
            currBlock = null;
            if(tag.equals(BlockTypes.CODE.toString()))
            {
               parseCode(node);
            }
            else if(tag.equals(BlockTypes.BLOCK.toString()))
            {
               parseBlock(node);
            }
            else if(tag.equals(BlockTypes.FLOW.toString()))
            {
               parseFlow(node);
            }
            else if(tag.equals(BlockTypes.EXPRESSION.toString()))
            {
               parseExpression(node);
            }
            else if(tag.equals(BlockTypes.VAR.toString()))
            {
               parseVar(node);
            }
            else if(tag.equals(BlockTypes.OBJECT.toString()))
            {
               //We should never hit this point
               throw new JJException("Error:Objects should only be defined in Vars.");
               //parseObject(node);
            }
            else if(tag.equals(BlockTypes.COMMENT.toString()))
            {
               parseComment(node);
            }
         }
      }
      
   }
   
   ///Scan through the list of refs and link them to their corresponding
   ///blocks.
   private void linkRefs()
   {
      for(int i = 0; i < refs.size(); i++)
      {
         refs.get(i).setBlock(blocks.get(new Integer(refs.get(i).getID())));
      }
   }
   
   public JJProject parseProject(String fileName)
      throws Exception
   {
      Document document = builder.parse(fileName);
      clear();
      document.getDocumentElement().normalize();
      if(!document.getDocumentElement().getNodeName().equals("project"))
      {
         throw new JJException("Error:Can only parse project documents");
      }
      NodeList nodes = document.getDocumentElement().getElementsByTagName("pname");
      for(int i = 0; i < nodes.getLength(); i++)
      {
         Node node = nodes.item(i);
         if(node.getNodeType() == Node.ELEMENT_NODE)
         {
            String pName = node.getChildNodes().item(0).getNodeValue();
            if(jjproj != null && pName != null && !(pName.length() == 0))
               throw new JJException("Error:Multiple top level name tags.");
            if(pName != null && !(pName.length() == 0)) jjproj = new JJProject(pName);
         }
      }
      if(jjproj == null) throw new JJException("Error:No project name defined.");
      
      currLib = null;
      nodes = document.getDocumentElement().getElementsByTagName("class");
      for(int i = 0; i < nodes.getLength(); i++)
      {
         Node node = nodes.item(i);
         if(node.getNodeType() == Node.ELEMENT_NODE && node.getChildNodes().getLength() > 1)
         {
            String cName = node.getChildNodes().item(1).getChildNodes().item(0).getNodeValue();
            JJClass jjclass = new JJClass(cName);
            jjproj.addClass(jjclass);
         }
      }
      
      nodes = document.getDocumentElement().getChildNodes();
      for(int i = 0; i < nodes.getLength(); i++)
      {
         Node node = nodes.item(i);
         if(node.getNodeType() == Node.ELEMENT_NODE)
         {
            String tag = ((Element)node).getTagName();
            if(tag.equals("name"))
            {
               //This has already been handled
            }
            else if(jjproj == null)
            {
               throw new JJException("Error:First element must be 'name'.");
            }
            else if(tag.equals("date"));
            //TODO do something with the date
            else if(tag.equals("class"))
            {
               parseClass(node);
            }
            else if(tag.equals("freeblocks"))
            {
               parseFree(node);
            }
            else if(tag.equals("tab"))
            {
               jjproj.addTab(parseTab(node));
            }
         }
         
//         Node node = nodes.item(i);
//         if (node != null && node.getNodeType() == Node.ELEMENT_NODE)
//         {
//            if(!((Element)node).getTagName().trim().isEmpty())
//               System.out.println(((Element)node).getTagName());
//         }
//         scanNode(node);
      }
      ///Currently we don't do anything with the libraries. Eventually we
      ///should add any classes/blocks that didn't already show up in the 
      ///project.
      nodes = document.getDocumentElement().getElementsByTagName("library");
      for(int i = 0; i < nodes.getLength(); i++)
      {
         Node node = nodes.item(i);
         if(node.getNodeType() == Node.ELEMENT_NODE)
         {
            String lName = node.getChildNodes().item(1).getChildNodes().item(0).getNodeValue();
            lName += ".jjl";
            jjproj.addLibrary(parseLibrary(lName));
         }
      }
      linkRefs();
      return jjproj;
   }
   
//   private void scanNode(Node n)
//   {
//      NodeList nodes;
//      if(n != null && n.hasChildNodes())
//      {
//         nodes = n.getChildNodes();
//         
//         for(int i = 0; i < nodes.getLength(); i++)
//         {
//            Node node = nodes.item(i);
//            if (node.getNodeType() == Node.ELEMENT_NODE)
//            {
//               if(!((Element)node).getTagName().trim().isEmpty())
//                  System.out.println(((Element)node).getTagName().trim());
//            }
//            if (node.getNodeType() == Node.TEXT_NODE)
//            {
//               if(!node.getNodeValue().trim().isEmpty())
//                  System.out.println(node.getNodeValue().trim());
//            }
//            scanNode(node);
//         }
//      }
//   }
   
   public JJLibrary parseLibrary(String fileName)
      throws Exception
   {
      return currLib;
   }
   
   public static void main(String[] args)
   {
      JJXMLParser parse;
      BVXMLWriter writer;
      try
      {
         parse = new JJXMLParser(DocumentBuilderFactory.newInstance());
         writer = new BVXMLWriter();
         JJProject helloWorld = parse.parseProject("Hello_World.jj");
         //System.out.println(helloWorld.toString());
         helloWorld.setName("hWorld");
         writer.visit(helloWorld);
         writer.finish();
         
      } catch (Exception e)
      {
         System.err.println("Error:");
         System.err.println(e.getMessage());
      }
   }
}
